package com.wanwu.channel.biz.impl;

import com.wanwu.channel.biz.PayBiz;
import com.wanwu.channel.common.enums.*;
import com.wanwu.channel.common.exception.SystemException;
import com.wanwu.channel.common.util.DateUtils;
import com.wanwu.channel.common.util.DingUtil;
import com.wanwu.channel.common.util.LogUtil;
import com.wanwu.channel.converter.ChannelPayConvert;
import com.wanwu.channel.converter.Converter;
import com.wanwu.channel.core.ChannelPayService;
import com.wanwu.channel.core.vo.ChannelPayVo;
import com.wanwu.channel.dao.entity.ChannelMerchantInfoEntity;
import com.wanwu.channel.dao.entity.ChannelPayEntity;
import com.wanwu.channel.dto.vo.PayQueryStrategyReqVo;
import com.wanwu.channel.dto.vo.PayQueryStrategyRespVo;
import com.wanwu.channel.dto.vo.PrepayStrategyReqVo;
import com.wanwu.channel.facade.dto.req.ChPayQueryReqDto;
import com.wanwu.channel.facade.dto.req.ChPrepayReqDto;
import com.wanwu.channel.facade.dto.resp.ChBaseRespDto;
import com.wanwu.channel.facade.dto.resp.ChPayQueryRespDto;
import com.wanwu.channel.facade.dto.resp.ChPrepayRespDto;
import com.wanwu.channel.core.util.apollo.ApolloMerchantUtil;
import com.wanwu.channel.service.strategy.PayStrategy;
import com.wanwu.channel.service.strategy.domain.PrepayStrategyResponse;
import com.wanwu.channel.core.tool.AlarmPoolUtil;
import com.wanwu.monitor.api.util.vo.AlarmMsgVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author xiaowei
 * @time 2020/4/11 11:51
 */
@Service("payBiz")
public class PayBizImpl implements PayBiz {
    private static Logger logger = LoggerFactory.getLogger(PayBizImpl.class);

    @Resource
    private Map<String, PayStrategy> payStrategyMap;

    @Resource
    private ChannelPayService channelPayService;

    @Resource
    private ApolloMerchantUtil apolloMerchantUtil;

    @Value("${mq.ding.appKey}")
    private String appKey;

    @Resource
    private AlarmPoolUtil alarmPoolUtil;

    /**
     * 预下单
     *
     * @param prepayReqDto
     * @return
     */
    @Override
    public ChPrepayRespDto prePay(ChPrepayReqDto prepayReqDto) {
        LogUtil.info(logger, "收到预下单请求：{0}", prepayReqDto);

        ChPrepayRespDto respDto = new ChPrepayRespDto();
        ChannelPayEntity channelPay = null;
        try {
            // 设置上报skyWalking的tag信息
            ActiveSpan.tag("业务订单号", prepayReqDto.getBizOrderNo());
            //支付渠道入参
            PrepayStrategyReqVo request = ChannelPayConvert.buildPrepayStrategyReq(prepayReqDto);
            //组入库实体
            channelPay = ChannelPayConvert.buildInsertChannelPay(prepayReqDto);
            channelPay.setCreateTime(new Date());
            request.setChannelNo(channelPay.getChannelNo());
            //临时兼容新老方案，0-使用支付系统配置信息，1-使用渠道系统配置信息
            if (1 == prepayReqDto.getMerchantConfigFlag()) {
                //使用Apollo的配置信息，不查询数据库
                ChannelMerchantInfoEntity merchantInfo = checkMerchantInfo(channelPay);
                channelPay.setMerchantNo(merchantInfo.getMerchantNo());
                channelPay.setAppId(merchantInfo.getMerchantAppId());
                request.setChannelMerchantInfoEntity(merchantInfo);
                request.setAppId(merchantInfo.getMerchantAppId());
                request.setCusId(merchantInfo.getMerchantNo());
                respDto.setMerchantName(merchantInfo.getMerchantName());
            }
            //入库
            ChannelPayVo vo = channelPayService.saveAndCheck(channelPay);
            //判断交易是否已经存在
            if (vo.isExist()) {
                respDto.setBizOrderNo(channelPay.getBizOrderNo());
                respDto.setRespCode(ExceptionEnum.REPEAT.name());
                respDto.setRespMsg(ExceptionEnum.REPEAT.getMsg());
                respDto.setMerchantNo(vo.getChannelPayEntity().getMerchantNo());
                respDto.setMerchantAppId(vo.getChannelPayEntity().getAppId());
                return respDto;
            }
            //获取支付通道
            PayType payType = PayType.getByCode(prepayReqDto.getPayType());
            //设置订单创建时间，做订单超时用
            request.setPayOrderCreateTime(channelPay.getCreateTime());
            //调用对应的支付通道
            PrepayStrategyResponse remoteResp = payStrategyMap.get(payType.getName()).generatePayParams(payType, request);
            respDto.setChannelNo(channelPay.getChannelNo());
            // 后处理，判断交易状态，更新库并且返回给支付系统，更新库操作后续实现
            if (TradeStatusEnum.SUCCESS == remoteResp.getTradeStatus()) {
                //交易成功
                //通联预下单处理
                channelPay.setTlOrderNo(remoteResp.getTrxId());
                channelPay.setThirdOrderNo(remoteResp.getChannelTrxId());
                respDto.setTlOrderNo(remoteResp.getTrxId());
                respDto.setThirdOrderNo(remoteResp.getChannelTrxId());
                //海关总署查询需要请求原文
                channelPay.setPrepayData(remoteResp.getPrepayReq());
                //海关总署查寻需要响应原文
                channelPay.setPrepayReturnData(remoteResp.getPrepayResp());
                respDto.setBizOrderNo(prepayReqDto.getBizOrderNo());
                respDto.setPayinfo(remoteResp.getPrepayOrderString());
                //通联H5支付页面调整地址
                respDto.setMWebUrl(remoteResp.getMWebUrl());
                respDto.setRespCode(ExceptionEnum.S00000.name());
                respDto.setRespMsg(ExceptionEnum.S00000.getMsg());
            } else {
                //交易失败
                respDto.setRespCode(remoteResp.getErrorCode());
                respDto.setRespMsg(remoteResp.getErrorMsg());
                channelPay.setPayStatus(ChannelPayStatus.FAIL.getValue());
            }
            respDto.setMerchantNo(channelPay.getMerchantNo());
            respDto.setBizOrderNo(channelPay.getBizOrderNo());
            respDto.setBizCode(channelPay.getBizCode());
            respDto.setMerchantAppId(channelPay.getAppId());
            channelPay.setErrorCode(remoteResp.getErrorCode());
            channelPay.setErrorMsg(remoteResp.getErrorMsg());
        } catch (Exception e) {
            if (null != channelPay) {
                channelPay.setPayStatus(ChannelPayStatus.FAIL.getValue());
                if (e instanceof SystemException) {
                    SystemException ex = (SystemException) e;
                    channelPay.setErrorCode(ex.getCode());
                    channelPay.setErrorMsg(ex.getMsg());
                } else {
                    channelPay.setErrorCode(ExceptionEnum.E10000.name());
                    channelPay.setErrorMsg(ExceptionEnum.E10000.getMsg());
                }
            }
            LogUtil.error(logger, e, "预下单处理失败");
            ActiveSpan.error("预下单处理失败");
            throw e;
        } finally {
            //更新数据库
            channelPayService.updateByChannelNo(channelPay);
        }
        LogUtil.info(logger, "预下单处理完成，返回报文：{0}", respDto);
        ActiveSpan.tag("通联支付单号", respDto.getTlOrderNo());
        ActiveSpan.tag("三方支付单号", respDto.getThirdOrderNo());
        ActiveSpan.tag("预下单返回信息", respDto.getRespMsg());
        return respDto;
    }


    @Override
    public ChPayQueryRespDto payQuery(ChPayQueryReqDto payQueryReqDto) {
        LogUtil.info(logger, "收到支付交易查询请求：{0}", payQueryReqDto);

        // 设置上报skyWalking的tag信息
        ActiveSpan.tag("业务订单号", payQueryReqDto.getBizOrderNo());

        ChPayQueryRespDto respDto = new ChPayQueryRespDto();
        try {
            //查询订单是否存在
            ChannelPayEntity channelPay = channelPayService.queryByBizOrderNoFMaster(payQueryReqDto.getBizOrderNo(), payQueryReqDto.getBizCode());
            if (ObjectUtils.isEmpty(channelPay)) {
                ActiveSpan.error(ExceptionEnum.E10011.getMsg());
                throw new SystemException(ExceptionEnum.E10011);
            }
            respDto.setMerchantNo(channelPay.getMerchantNo());
            respDto.setMerchantAppId(channelPay.getAppId());
            //respDto.setBizOrderNo(channelPay.getBizOrderNo());
            //respDto.setBizCode(channelPay.getBizCode());
            //兼容微信返回，不为处理中，不为未支付则视为终态
            if (!ChannelPayStatus.PROCESSING.getValue().equals(channelPay.getPayStatus()) &&
                    !ChannelPayStatus.NOTPAY.getValue().equals(channelPay.getPayStatus())) {
                //查到终态直接返回
                respDto = Converter.buildPayQuryRespVo(channelPay);
                respDto.setRespCode(ExceptionEnum.S00000.name());
                respDto.setRespMsg(ExceptionEnum.S00000.getMsg());
                LogUtil.info(logger, "交易已为终态，直接返回：{0}", respDto);
                return respDto;
            }
            //组查询请求
            PayQueryStrategyReqVo request = ChannelPayConvert.buildPayQueryReq(channelPay);
            //使用Apollo的配置信息，不查询数据库
            ChannelMerchantInfoEntity entity = apolloMerchantUtil.selectByPayChannelAndMerchantNoAndBizCode(payQueryReqDto.getPayType() + "", payQueryReqDto.getMerchantNo(), payQueryReqDto.getBizCode());
            request.setChannelMerchantInfoEntity(entity);
            // 没有终态的再去调支付宝/微信 查询交易
            PayType payType = PayType.getByCode(payQueryReqDto.getPayType());
            request.setMerchantNo(payQueryReqDto.getMerchantNo());
            request.setMerchantAppId(payQueryReqDto.getMerchantAppId());
            PayQueryStrategyRespVo remoteResp = payStrategyMap.get(payType.getName()).queryPayHttp(payType, request);
            //后处理
            respDto = handlePayQuery(remoteResp, channelPay);
        } catch (Exception e) {
            LogUtil.error(logger, e, "支付交易查询失败");
            ActiveSpan.error("支付交易查询失败");
            handleException(respDto, e);
        }
        LogUtil.info(logger, "支付交易查询完成，返回：{0}", respDto);
        ActiveSpan.tag("通联支付单号", respDto.getTlOrderNo());
        ActiveSpan.tag("三方支付单号", respDto.getThirdOrderNo());
        ActiveSpan.tag("支付状态", respDto.getStatus());
        ActiveSpan.tag("预下单返回信息", respDto.getRespMsg());
        return respDto;
    }


    /**
     * 处理查询结果
     *
     * @param remoteResp
     * @return
     */
    private ChPayQueryRespDto handlePayQuery(PayQueryStrategyRespVo remoteResp, ChannelPayEntity channelPay) {
        ChPayQueryRespDto response = new ChPayQueryRespDto();
        response.setTlOrderNo(remoteResp.getTlOrderNo());
        response.setThirdOrderNo(remoteResp.getThirdOrderNo());
        response.setBizOrderNo(channelPay.getBizOrderNo());
        response.setBizCode(channelPay.getBizCode());
        response.setChannelNo(channelPay.getChannelNo());
        if (StringUtils.isEmpty(remoteResp.getFeeAmount())) {
            response.setFeeAmount(BigDecimal.ZERO);
        } else {
            response.setFeeAmount(new BigDecimal(remoteResp.getFeeAmount()));
        }
        ChannelPayStatus payStatus = remoteResp.getChannelPayStatus();
        //非支付查询结果订单状态则钉钉告警
        if(null == payStatus || Boolean.TRUE.equals(ChannelPayStatus.shouldNotExist(payStatus))){
//            DingUtil.sendDingAtPeople(appKey, "支付单结果查询出错",
//                    "支付单结果查询查到不该出现的状态\n\n状态：" + payStatus + "\n\nbizOrderNo：" + channelPay.getBizOrderNo(), null);
            AlarmMsgVo alarmMsgVo = new AlarmMsgVo.Builder().head("渠道系统告警：支付单结果查询查到不该出现的状态")
                    .field("状态", payStatus.getDesc())
                    .field("bizOrderNo", channelPay.getBizOrderNo()).build();
            alarmPoolUtil.addGatherAlarm(appKey, AlarmTypeEnum.QURYPAYORDERFAIL, alarmMsgVo);
            throw new SystemException(ExceptionEnum.E10037, "支付单结果查询查到不该出现的状态状态：" + payStatus);
        }
        response.setStatus(payStatus.getValue() + "");
        response.setBuyerId(remoteResp.getBuyerId());
        response.setBuyerEmail(remoteResp.getBuyerEmail());
        ChannelPayEntity newRecord = new ChannelPayEntity();
        newRecord.setBizOrderNo(channelPay.getBizOrderNo());
        newRecord.setBizCode(channelPay.getBizCode());
        newRecord.setThirdOrderNo(remoteResp.getThirdOrderNo());
        newRecord.setPayStatus(payStatus.getValue());
        if (ChannelPayStatus.SUCCESS == payStatus) {
            newRecord.setPayTime(remoteResp.getFinishTime());
            response.setFinishTime(DateUtils.get(1, newRecord.getPayTime()));
            //海关总署-通联H5支付方式返回报文，落的是查询、回调报文,其他支付方式的queryResp为空，不会保存落库
            newRecord.setPrepayReturnData(remoteResp.getQueryResp());
            newRecord.setErrorCode(ExceptionEnum.S00000.name());
            newRecord.setErrorMsg(ExceptionEnum.S00000.getMsg());
        } else {
            newRecord.setErrorCode(remoteResp.getErrCode());
            newRecord.setErrorMsg(remoteResp.getErrMsg());
            response.setThirdErrCode(remoteResp.getErrCode());
            response.setThirdErrMsg(remoteResp.getErrMsg());
        }
        //数据库更新
        channelPayService.checkAndUpdateByChannelNo(newRecord);
        response.setRespCode(ExceptionEnum.S00000.name());
        response.setRespMsg(ExceptionEnum.S00000.getMsg());
        return response;
    }

    /**
     * 获取并筛选可用商户号
     *
     * @param channelPay
     * @return
     */
    private ChannelMerchantInfoEntity checkMerchantInfo(ChannelPayEntity channelPay) {
        List<ChannelMerchantInfoEntity> entityList = apolloMerchantUtil.selectByPayType(channelPay);
        if (CollectionUtils.isEmpty(entityList)) {
            DingUtil.sendDingAtPeople(appKey, "不存在可用商户号",
                    "不存在可用商户号，请检查配置\n\n bizOrderNo：" + channelPay.getBizOrderNo(), null);
            throw new SystemException(ExceptionEnum.E10001);
        }
        if (1 == entityList.size()) {
            return entityList.get(0);
        }
        int bizHash = channelPay.getBizOrderNo().hashCode();
        int mod = bizHash % entityList.size();
        if (mod < 0) {
            mod = Math.abs(mod);
        }
        return entityList.get(mod);
    }

    /**
     * 异常处理
     *
     * @param respDto
     * @param e
     * @param <T>
     */
    private <T extends ChBaseRespDto> void handleException(T respDto, Exception e) {
        if (e instanceof SystemException) {
            SystemException ex = (SystemException) e;
            respDto.setRespCode(ex.getCode());
            respDto.setRespMsg(ex.getMsg());
        } else {
            respDto.setRespCode(ExceptionEnum.E10000.name());
            respDto.setRespMsg(ExceptionEnum.E10000.getMsg());
        }
    }
}
